package edu.predatorwars.client.visualization;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;



import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;

import edu.predatorwars.shared.models.Creature;
import edu.predatorwars.shared.models.GameInfo;
import edu.predatorwars.shared.models.Location;
import edu.predatorwars.shared.models.Move;
import edu.predatorwars.shared.models.Player;
import edu.predatorwars.shared.models.Resource;
import edu.predatorwars.shared.models.Terrain;
import edu.predatorwars.client.Client;
import java.awt.Point;


public class VisualizationStarter implements VisualizationCallbackInterface {
	
	Thread thread;
	String title;
	Visualization vis;
	LwjglApplication lwjgl;
	Client client;

	VisLocation[][] prevVisMap;
	List<VisPlayer> prevPlayers;
	List<VisResource> prevResources;
	
	
	
	public VisualizationStarter(Client c, String gameName) {
		
		title = gameName;
		client = c;
		prevVisMap = new VisLocation[0][0];
		prevPlayers = new ArrayList<VisPlayer>();
		prevResources = new ArrayList<VisResource>();
		this.start();
	}
	
	

	public void start() {
		
		vis = new Visualization(this);
	
		
		LwjglApplicationConfiguration lwconfig = new LwjglApplicationConfiguration();
		lwconfig.fullscreen = false;
		lwconfig.width = 1280;
		lwconfig.height = 720;
		lwconfig.vSyncEnabled = false;
		lwconfig.samples = 3;

	
		lwconfig.title = title;
		lwconfig.useGL20 = true;
	
		//lwjgl = new LwjglApplication(vis, title, 1280, 720, true);

		lwjgl = new LwjglApplication(vis, lwconfig);
	}
	
	
	
	/**
	 * Called by client and run on client thread.  
	 * Updates visualization with all elements of the board including terrain, creatures, and resources.
	 * @param locationArray
	 */
	
	public void setBoard(Location[][] locationArray) {
		List<PlayerExt> players = new ArrayList<PlayerExt>();
		List<ResourceExt> resources = new ArrayList<ResourceExt>();
		TerrainExt[][] terrainArray = new TerrainExt[locationArray.length][];
		final VisLocation[][] nextVisMap = new VisLocation[locationArray.length][];
		
		//create new extended terrain, players, and resources
		for (int i=0;i<locationArray.length;i++) {
			terrainArray[i] = new TerrainExt[locationArray[i].length];
			nextVisMap[i] = new VisLocation[locationArray[i].length];
			for (int j=0;j< locationArray[i].length;j++) {
				Terrain current = locationArray[i][j].getTerrain();
				terrainArray[i][j] = new TerrainExt(current,i,j,current.isTraversable(), current.isSightBlocked());
				nextVisMap[i][j] = new VisLocation();
				Player player = locationArray[i][j].getPlayer();
				Resource resource = locationArray[i][j].getResource();
				if (player != null) players.add(new PlayerExt(player, i, j));
				if (resource != null) resources.add(new ResourceExt(resource, i, j));
			}
		}
		
		//*lists to be sent to visualization*/
		final List<DataSet<VisTerrain, FPoint>> terrain_toAdd = new ArrayList<DataSet<VisTerrain,FPoint>>();
		final List<DataSet<VisTerrain, TerrainExt>> terrain_toSwap = new ArrayList<DataSet<VisTerrain,TerrainExt>>();
		final List<VisTerrain> terrain_toDispose = new ArrayList<VisTerrain>();
		final List<VisPlayer> player_toAdd = new ArrayList<VisPlayer>();
		final List<TDataSet<VisPlayer, PlayerExt, Point>> player_toUpdate = new ArrayList<TDataSet<VisPlayer,PlayerExt,Point>>();
		final List<VisPlayer> player_toRemove = new ArrayList<VisPlayer>();
		final List<VisResource> resource_toAdd = new ArrayList<VisResource>();
		final List<VisResource> resource_toRemove = new ArrayList<VisResource>();
		
		setTerrain(terrainArray, nextVisMap, terrain_toAdd, terrain_toSwap, terrain_toDispose);
		setPlayers(players, nextVisMap, player_toAdd, player_toUpdate, player_toRemove);
		setResources(resources, nextVisMap, resource_toAdd, resource_toRemove);
		
		prevVisMap = nextVisMap;
	

		lwjgl.postRunnable(new Runnable() {
			public void run () {
				vis.setBoard(prevVisMap);
				vis.swapTerrain(terrain_toSwap);
				vis.addTerrain(terrain_toAdd);	
				vis.disposeTerrain(terrain_toDispose);
				vis.addPlayers(player_toAdd);
				vis.updatePlayers(player_toUpdate);
				vis.removePlayers(player_toRemove);
				vis.addResources(resource_toAdd);
				vis.removeResources(resource_toRemove);
				
				
				}
		});
		
		
	}
	
	
	
	
	//compare new map to previous map
	//swap changes terrains
	//create new terrains
	//dispose lost terrains
	
	//terrain swap is working correctly
	//TODO: fix dispose and add. (low priority and only if this type of change is supported by game engine)
	
	
	private void setTerrain(TerrainExt[][] ta, VisLocation[][] nextVisMap, List<DataSet<VisTerrain, FPoint>> terrain_toAdd, List<DataSet<VisTerrain, TerrainExt>> terrain_toSwap,
			List<VisTerrain> terrain_toDispose) 
	{

		float x,y;
		int a=0;
		for (;a<ta.length ;a++) {
			y = MathExt.calcYPositionFromArrayLoc(a, ta.length);  //i*0.32f;
	
			int b=0;
			for (;b<ta[a].length ;b++) {
	
				if (a >= prevVisMap.length || b>= prevVisMap[a].length) {
					x = MathExt.calcXPositionFromArrayLoc(a, b, ta.length, ta[a].length);	//j*0.41f+(i%2f)*0.2f;
					VisTerrain vt = new VisTerrain(ta[a][b]);
					nextVisMap[a][b].setTerrain(vt);
					terrain_toAdd.add(new DataSet<VisTerrain,FPoint>(vt, new FPoint(x,y)));
				}
				else if (prevVisMap[a][b].getTerrain().getTerrain().getTerrainType() != ta[a][b].getTerrainType()) {
					VisTerrain vt = prevVisMap[a][b].getTerrain();
					nextVisMap[a][b].setTerrain(vt);
					terrain_toSwap.add(new DataSet<VisTerrain,TerrainExt>(vt,ta[a][b]));

				}
				else
				{

					nextVisMap[a][b].setTerrain(prevVisMap[a][b].getTerrain());
				}
			}
			if (a < prevVisMap.length)
			for (;b<prevVisMap[a].length;b++) {
				System.out.println("disposed");
				terrain_toDispose.add(prevVisMap[a][b].getTerrain());
			}
		}
		for(;a<prevVisMap.length;a++) {
			for (int b=0;b<prevVisMap[a].length;b++) {
				terrain_toDispose.add(prevVisMap[a][b].getTerrain());
			}
		}

		
	}
	
	//check for player movement changes
	//check for new players
	//check for players that left
	//players should only be updated after terrain has been updated
	
	private void setPlayers(List<PlayerExt> players,VisLocation[][] nextVisMap, List<VisPlayer> player_toAdd, List<TDataSet<VisPlayer, PlayerExt, Point>> player_toUpdate,
			List<VisPlayer> player_toRemove) {

		int i;
	
		List<VisPlayer> nextPlayers=  new ArrayList<VisPlayer>();
		
		for (i=0;i<players.size();i++) {
			int f;
			Point len = new Point(nextVisMap.length,nextVisMap[players.get(i).getPlayerLocation().x].length);
			if ((f=prevPlayers.indexOf(players.get(i)))!= -1) {
				VisPlayer vplayer = prevPlayers.get(f);
				//if (!vplayer.getPlayer().getPlayerLocation().equals(players.get(i).getPlayerLocation())) {	
					player_toUpdate.add(new TDataSet<VisPlayer, PlayerExt, Point>(vplayer, players.get(i), len));
					
				//}
				nextPlayers.add(vplayer);
				Point p = players.get(i).getPlayerLocation();
				nextVisMap[p.x][p.y].setPlayer(vplayer); 
			}
			else
			{
				
				//create new player
				PlayerExt player = players.get(i);
				VisPlayer vplayer = new VisPlayer(player, len.x, len.y);
				player_toAdd.add(vplayer);
				nextPlayers.add(vplayer);
				Point p = player.getPlayerLocation();
				nextVisMap[p.x][p.y].setPlayer(vplayer);
			}

		}
		

		prevPlayers=  nextPlayers;

		
	}
	
	private void setResources(List<ResourceExt> resources, VisLocation[][] nextVisMap, 	List<VisResource> resource_toAdd, List<VisResource> resource_toRemove) {
		
		List<VisResource> nextResources = new ArrayList<VisResource>();
		
		for (int i=0;i<resources.size();i++) {

			ResourceExt resource = resources.get(i); 
			Point loc = resource.getResourceLocation();
			Point len = new Point(nextVisMap.length,nextVisMap[loc.x].length);
			
			
			
			VisResource prevmapvr = null;
			if (prevVisMap.length > loc.x && prevVisMap[loc.x].length > loc.y) {
				prevmapvr = prevVisMap[loc.x][loc.y].getResource();
		
			}
		
			if (prevmapvr != null && prevmapvr.getResource().getResourceType() == resource.getResourceType()){
				nextResources.add(prevmapvr);
				nextVisMap[loc.x][loc.y].setResource(prevmapvr);
				prevResources.remove(prevmapvr);
			
			}
			else
			{
				VisResource vr; 
				if (resource.getResourceType() != Resource.WATER)
					vr = new VisResource(resource,len.x,len.y, true);
				else vr= new VisResource(resource, len.x, len.y, false);
				resource_toAdd.add(vr);
				nextResources.add(vr);
				nextVisMap[loc.x][loc.y].setResource(vr);
			
			}
			
		}
		for (int i=0;i<prevResources.size();i++) {
			resource_toRemove.add(prevResources.get(i));
	
		}
		
		prevResources = nextResources;

	}
	
		
	public void setMoves(List<Move> moves) {
		final List<Point> points = new ArrayList<Point>();
		for (int i=0;i<moves.size();i++) {
			points.add( new Point(moves.get(i).getTargetPoint() ));
		}
		
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.setMoves(points);
			}
		});
	}
	
	public void showMyTurn(final Boolean b) {
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.showMyTurn(b);
			}
		});
	}
	
	public void setMyPlayer(Player player) {
		PlayerExt pePlayer = new PlayerExt(player,0,0); 
		VisPlayer myPlayer;
		//PlayerExt can be compared to visplayer (overriden equals)
		if (!prevPlayers.contains(pePlayer)) { 
			myPlayer = new VisPlayer(pePlayer);
			prevPlayers.add(myPlayer);
		}
		else 
			myPlayer = prevPlayers.get(prevPlayers.indexOf(pePlayer));
		
		vis.setMyPlayer(myPlayer);
	}
	
	public void setTurnsLeft(Integer turns) {
		final String turnsLeft = "Moves Left:  " + turns.toString() ;
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.setTurnsLeft(turnsLeft);
			}
		});
	}
	
	public void exit() {
		lwjgl.exit();
	}
	
	public void showWin(final Boolean b) {
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.showWin(b);
			}
		});
	}
	
	public void showLose(final Boolean b) {
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.showLose(b);
			}
		});
	}
	
	public void showDisconnected(final Boolean b) {
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.showDisconnected(b);
			}
		});
	}

	@Override
	public void endedTurn() {
		client.postRunnable(new Runnable() {
			public void run() {
				client.endedTurn();
			}
		});
	}

	
	
	/*Game Starter Stuff*/
	public void showGames() {
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.showGames();
			}
		});
		
	}
	
	public void showWaiting() {
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.showWaiting();
			}
		});
	}
	
	public void showCreatures() {
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.showCreatures();
			}
		});
	}
	
	
	public void setGames(List<GameInfo> games) {
		final ArrayList<String> game_names = new ArrayList<String>();
		final ArrayList<UUID> game_uuids = new ArrayList<UUID>();
		
		//reconstruct game list--dont want client to change something while rendering
		for (int i=0;i<games.size();i++) {
			GameInfo curr = games.get(i);
			UUID uuid = curr.getGameId();
			String name = new String(curr.getMapName());
			
			//do some alignment formatting
			int len = name.length();
			for (int j=len;j<15;j++) {
				name = " " + name;
			}
			len = name.length();
			for (int j=len;j<30;j++) {
				name = name + " ";
			}
			
			//show #of players
			name = name + "(" + curr.getCurrentPlayers() + "/" + curr.getMaxPlayers() + ")";
			
			game_names.add(new String(name));
			game_uuids.add(new UUID(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()));
		}
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.setGames(game_uuids, game_names);
			}
		});
	}
	
	
	@Override
	public void chosenGame(final UUID gameID) {
		client.postRunnable(new Runnable() {
			public void run() {
				client.chosenGame(gameID);
			}
		});
	}


	public void setCreatures(final List<Creature> creatures) {
		final ArrayList<Creature> nc= new ArrayList<Creature>();
		for (int i=0;i<creatures.size();i++)
			nc.add(new Creature(creatures.get(i)));
		lwjgl.postRunnable(new Runnable() {
			public void run() {
				vis.setCreatures(nc);
			
			}
		});
	}
	
	@Override
	public void chosenAnimal(Creature animal) {
		// TODO Auto-generated method stub
		
	}



	@Override
	public void exitGame() {
		client.postRunnable(new Runnable() {
			public void run() {
				client.exitGame();
			}
		});
		
	}




	@Override
	public void moved(final Point location) {
		client.postRunnable(new Runnable() {
			public void run() {
				client.moved(location);
			}
		});
		
	}
	
	@Override
	public void confirmedModalDialog() {
		client.postRunnable(new Runnable() {
			public void run() {
				client.confirmedModalDialog();
			}
		});
	}



	@Override
	public void createGame(final String mapName, final int maxPlayers) {
		client.postRunnable(new Runnable() {
			public void run() {
				client.createGame(mapName, maxPlayers);
			}
		});
	}

	
	
	
}